package com.chb.config;

import com.chb.authentication.CustomSavedRequestAwareAuthenticationSuccessHandler;
import com.chb.authentication.RestfulExceptionHandler;
import com.chb.authentication.SimpleUserDetailsService;
import com.chb.filter.CustomFilterSecurityInterceptor;
import com.chb.jwt.JwtAuthenticationTokenFilter;
import com.chb.properties.SecurityConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.annotation.Resource;
import java.util.*;

/**
 * @description:
 * @author: CHB
 * @create: 2021-10-28 15:52
 **/
@Slf4j
@Configuration
@EnableWebSecurity
// 开启注解权限控制
@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private SecurityConfigProperties properties;

    @Resource
    private RestfulExceptionHandler exceptionHandler;

    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        log.info("----JwtSecret------{}---------",properties.getJwtSecret());
        // 禁用跨域请求伪造
        http.csrf().disable();
        // 禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 放行登录请求，拦截其他所有请求
        http.authorizeRequests()
                .antMatchers("/login","/login/**","**/login","**/login/**").permitAll()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
        // 校验token
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 认证授权异常处理
        http.exceptionHandling()
                .accessDeniedHandler(exceptionHandler)
                .authenticationEntryPoint(exceptionHandler);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 不拦截的路径，资源
        web
                .ignoring().antMatchers(properties.getAnonymousPath());

    }

    /**
     * AuthenticationManager 负责调用 UserDetailsService验证用户名密码
     * @author CHB
     * @date 2022-06-20 17:41:12
     * @return AuthenticationManager
     */
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    // 验证用户密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Bean
    public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
        return new CustomSavedRequestAwareAuthenticationSuccessHandler();
    }

    @Bean
    public UserDetailsService customUserDetailsService(){
        return new SimpleUserDetailsService();
    }

    // 动态权限控制
    private FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
        CustomFilterSecurityInterceptor filterSecurityInterceptor = new CustomFilterSecurityInterceptor();
        filterSecurityInterceptor.setSecurityMetadataSource(new DefaultFilterInvocationSecurityMetadataSource(obtainRequestMap()));
        filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
        filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
        return filterSecurityInterceptor;
    }

    private AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
        voters.add(new RoleVoter());
        return new AffirmativeBased(voters);
    }

    // 模拟数据库添加权限
    private LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> obtainRequestMap() {

        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
//        HashSet<ConfigAttribute> configAttributes = new HashSet<>();
//        configAttributes.add(new org.springframework.security.access.SecurityConfig("ROLE_C"));
        // 注意此处，我们设置ConfigAttribute为 ROLE_ 前缀加上角色标识，与 CustomJdbcUserDetailsService 里面组织UserDetails设置角色标识呼应
        requestMap.put(new AntPathRequestMatcher("/m1"),Set.of(new org.springframework.security.access.SecurityConfig("ROLE_A")));
        requestMap.put(new AntPathRequestMatcher("/m2"),Set.of(new org.springframework.security.access.SecurityConfig("ROLE_B")));
        requestMap.put(new AntPathRequestMatcher("/m3"),Set.of(new org.springframework.security.access.SecurityConfig("ROLE_C")));
        return requestMap;
    }
}
